<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="./bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="./bootstrap/bootstrap-theme.min.css" />
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }


        .wrap {
            width: 100px;
            height: 40px;
            background-color: red;
            text-align: center
        }

        .wrap p {

            width: 100%;
            height: 100%;
            line-height: 2;
            text-align: center;
        }

        #file {
            position: absolute;
            left: 0;
            top: 0;
            width: 100px;
            height: 40px;
            display: block;
            opacity: 0;
        }

        .progress {
            position: relative;
        }

        .progress-bar {
            transition: width .3s ease
        }

        .progress .value {
            position: absolute;
            color: #FF9800;
            left: 50%;
        }

        .container {
            width: 500px;
        }

        .row {
            border-bottom: 1px solid gray;
            padding: 10px;
        }

        .hidden {
            display: none;
        }
    </style>
    <title>上传文件</title>
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-4">点击上传按钮</div>
            <div class="col-md-8">
                <div class="wrap btn btn-default">
                    <input type="file" id="file" />
                    <p>上传文件</p>
                </div>
            </div>
        </div>
        <div class="row" id="process1" style="display: none">
            <div class="col-md-4">校验文件进度</div>
            <div class="col-md-8">
                <div class="progress">
                    <div id="checkProcessStyle" class="progress-bar" style="width:0%"></div>
                    <p id="checkProcessValue" class="value">0%</p>
                </div>
            </div>
        </div>
        <div class="row" id="process2" style="display: none">
            <div class="col-md-4">上传文件进度</div>
            <div class="col-md-8">
                <div class="progress">
                    <div id="uploadProcessStyle" class="progress-bar" style="width:0%"></div>
                    <p id="uploadProcessValue" class="value">0%</p>
                </div>
            </div>
        </div>
    </div>
    <script src="./lib/jquery-1.10.2.min.js"></script>
    <script src="./bootstrap/bootstrap.min.js"></script>
    <script src="./lib/spark-md5.min.js"></script>
    <script>
        let baseUrl = 'http://localhost:5000'
        let chunkSize = 5 * 1024 * 1024
        let fileSize = 0
        let file = null
        let hasUploaded = 0
        let chunks = 0
        // spark = new SparkMD5.ArrayBuffer()
        $("#file").on('change', function () {
            file = this.files[0]
            fileSize = file.size;
            responseChange(file)
        })

        // 0.响应点击
        async function responseChange(file) {
            // 第一步：按照 修改时间+文件名称+最后修改时间-->MD5
            // 显示文件校验进度
            $("#process1").slideDown(200)
            // 开始校验
            let fileMd5Value = await md5File(file)
            // 第二步：校验文件的MD5
            let result = await checkFileMD5(file.name, fileMd5Value)
            // 如果文件已存在, 就秒传
            if (result.file) {
                alert('文件已秒传')
                return
            }
            // let exit = false
            // 显示文件上传进度
            $("#process2").slideDown(200)
            // 第三步：检查并上传MD5
            await checkAndUploadChunk(fileMd5Value, result.chunkList)
            // 第四步: 通知服务器所有分片已上传完成
            notifyServer(fileMd5Value)
        }

        // 1.修改时间+文件名称+最后修改时间-->MD5
        function md5File(file) {
            return new Promise((resolve, reject) => {
                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                    //chunkSize = 2097152, // Read in chunks of 2MB
                    chunkSize = file.size / 100,
                    //chunks = Math.ceil(file.size / chunkSize),
                    chunks = 100,
                    currentChunk = 0,
                    spark = new SparkMD5.ArrayBuffer(),
                    fileReader = new FileReader();

                fileReader.onload = function (e) {
                    console.log('read chunk nr', currentChunk + 1, 'of', chunks);
                    spark.append(e.target.result); // Append array buffer
                    currentChunk++;

                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        let cur = +(new Date())
                        console.log('finished loading');
                        // alert(spark.end() + '---' + (cur - pre)); // Compute hash
                        let result = spark.end()
                        resolve(result)
                    }
                };

                fileReader.onerror = function () {
                    console.warn('oops, something went wrong.');
                };

                function loadNext() {
                    var start = currentChunk * chunkSize,
                        end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;

                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                    $("#checkProcessStyle").css({
                        width: (currentChunk + 1) + '%'
                    })
                    $("#checkProcessValue").html((currentChunk + 1) + '%')
                    // $("#tip").html(currentChunk)
                }

                loadNext();
            })
        }
        // 2.校验文件的MD5
        function checkFileMD5(fileName, fileMd5Value) {
            return new Promise((resolve, reject) => {
                let url = baseUrl + '/check/file?fileName=' + fileName + "&fileMd5Value=" + fileMd5Value
                $.getJSON(url, function (data) {
                    resolve(data)
                })
            })
        }
        // 3.上传chunk
        async function checkAndUploadChunk(fileMd5Value, chunkList) {
            chunks = Math.ceil(fileSize / chunkSize)
            hasUploaded = chunkList.length
            for (let i = 0; i < chunks; i++) {
                let exit = chunkList.indexOf(i + "") > -1
                // 如果已经存在, 则不用再上传当前块
                if (!exit) {
                    let index = await upload(i, fileMd5Value, chunks)
                    hasUploaded++
                    let radio = Math.floor((hasUploaded / chunks) * 100)
                    $("#uploadProcessStyle").css({
                        width: radio + '%'
                    })
                    $("#uploadProcessValue").html(radio + '%')
                }
            }
        }

        // 3-2. 上传chunk
        function upload(i, fileMd5Value, chunks) {
            return new Promise((resolve, reject) => {
                //构造一个表单，FormData是HTML5新增的
                let end = (i + 1) * chunkSize >= file.size ? file.size : (i + 1) * chunkSize
                let form = new FormData()
                form.append("data", file.slice(i * chunkSize, end)) //file对象的slice方法用于切出文件的一部分
                form.append("total", chunks) //总片数
                form.append("index", i) //当前是第几片     
                form.append("fileMd5Value", fileMd5Value)
                $.ajax({
                    url: baseUrl + "/upload",
                    type: "POST",
                    data: form, //刚刚构建的form数据对象
                    async: true, //异步
                    processData: false, //很重要，告诉jquery不要对form进行处理
                    contentType: false, //很重要，指定为false才能形成正确的Content-Type
                    success: function (data) {
                        resolve(data.desc)
                    }
                })
            })

        }

        // 第四步: 通知服务器所有分片已上传完成
        function notifyServer(fileMd5Value) {
            let url = baseUrl + '/merge?md5=' + fileMd5Value + "&fileName=" + file.name + "&size=" + file.size
            $.getJSON(url, function (data) {
                alert('上传成功')
            })
        }

        function getDate() {
            let d = new Date()
            return d.getMinutes() + ':' + d.getSeconds() + ' ' + d.getMilliseconds()
        }
    </script>
</body>

</html>